LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

voice_assistant

2022/2/20

制作语音助手

思路

制作语音助手其实不难,只是步骤有点繁琐,但是其实每一步都挺简单的。

步骤大致分为以为5点

1.将输入保存成语音文件(RECORD)

2.读取语音文件获得文本(ASR)

3.对文本处理输出所需要的文本(NLP)

4.将输出文本转换成语音文件(TTS)

5.将语音文件播出(PLAY)


其中第一点和第五点可以放到一起,支持录音的一般也支持播音。

其中第二点和第四点可以放到一起,支持语音识别的一般也支持语音合成。

第三点可以简单对文本处理,实现任务型的语音助手,

也可以用神经网络,也有现成的图灵机器人,实现交流型的语音助手。


RECORD和PLAY

这一方面可以用pyaudio实现,我也实际实践过

但是它封装后可设置得参数指向性不是很明显

而且效果也没有系统自带的好

所以最后选择了用python执行命令行程序

调用系统自带的arecord来RECORD,用aplay来PLAY

即方便又简洁还好用。


pyaudio

pyaudio代码可以参考源码:http://people.csail.mit.edu/hubert/pyaudio/

其中有几个地方要注意

1.录音和播音过程会出现报错信息,但是不影响录音,可以关闭信息

import sys,os

os.close(sys.stderr.fileno()) 

2.play的代码有问题

# 源码
data = wf.readframes(CHUNK)

while data != '':
    stream.write(data)
    data = wf.readframes(CHUNK)

# while的判断有问题 因为就算readframes读到最后 返回的也是b'' != '' 所以是死循环
# 应该改成 while len(data) >0:

# 或者直接改成下面这样子
while True:
    data = wf.readframes(CHUNK)
    if len(data) > 0:
        stream.write(data)
    else:
        break

3.有bug时注意查看播放设备对不对以及其他各个参数

pyaudio的文档:http://people.csail.mit.edu/hubert/pyaudio/docs/

stream类有挺多参数可以设置的


系统自带arecord

  1. 查看可录音设备
arecord -l

留意card和device后面的数字 例如card 1 device 0


  1. 录音
arecord -D plughw:1,0 -d 5 -f cd -r 16000 -c 1 -t wav test.wav

hw代表直接访问硬件,plughw代表经过采样率和格式转换插件。

-D 指定设备 hw:1,0 代表card1 device0

-d 指定录音时间

-f 指定录音格式

-r 指定采样率

-c 指定采样通道

-t 指定文件格式


  1. python调用
import os
os.system("arecord -D plughw:1,0 -d 5 -f cd -r 16000 -c 1 -t wav test.wav")

参考:https://blog.csdn.net/z2066411585/article/details/99089141

参考:https://blog.csdn.net/xiongtiancheng/article/details/80577478


系统自带aplay

  1. 查看可播放设备
aplay -l

留意card和device后面的数字 例如card 1 device 0


  1. 播放
aplay -D plughw:1,0 test.wav

  1. python调用
import os
os.system("aplay -D plughw:1,0 test.wav")

ASR和TTS

这一方面我使用 百度语音识别 直接看官方的例程和开发文档

网址:https://cloud.baidu.com/doc/SPEECH/s/Vk38lxily


首先要创建一个应用,获得 API Key和Secret Key,然后套入官方代码就可以使用了。

代码使用官方代码:https://github.com/Baidu-AIP/speech-demo

API Key和Secret Key不要手输!!直接复制粘贴!!

因为真的会分辨不了,我把I(大写i)输错成l(小写L),debug了好久!


可以先将API_KEY和SECRET_KEY带入到下面网址,然后输入到网页验证是否可用

http://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=API_KEY&client_secret=SECRET_KEY

补充一下ASR后续的处理

输出是一个很长的字符串,假设是’“xxx”:[xxx],“result”:[“你好。”]’

我们需要将其中的你好。提取出来

我使用的是正则表达式,先匹配"result":[],

然后再把输出结果切片获得我们所需的文本,

注意末尾是有标点符号的,但是我们不要将其剔除掉

因为语音合成会将标点符号也计算进去。

代码如下

#solve output
match = re.search('"result":\[(.*)]', result_str)#正则表达式匹配
if match:
    start, end = match.span()
    result = result_str[start + 11:end - 2]#切片获得所需文本
    print(result,'\n')

各个模块实现了,接下来就是中间的逻辑处理了,大致处理如下,叮咚为唤醒名称。

def record(device,time,rate,file):
    print(" * RECORD")
    cmd = "arecord -D plughw:"+\
          str(device)+\
          " -d " +\
          str(time)+\
          " -f cd -r "+\
          str(rate)+\
          " -c 1 -t wav " +\
          file
    os.system(cmd)
    print()

def play(device,file):
    print(" * PLAY")
    cmd = "aplay -D plughw:"+\
          device+\
          " " + \
          file
    os.system(cmd)
    print()

    
flag = False # 唤醒flag
cnt = 0 # 非任务语音计数
while True:
    output_text = " " # 输出的文本

    # 录音并语音识别
    record(device,record_time,rate,"./record.wav")
    text = asr("./record.wav",rate)

    if flag:
        # 这里就通过if elif 罗列所需要的任务
        if 0:
            pass
        elif 0:
            pass           
        else:
            cnt += 1
            output_text = " "

    # 计数大于一定值则结束唤醒
    if cnt >5:
        flag = False
        cnt = 0

    if "叮咚" in text:
        flag = True
        output_text = "在"

    print("输出语音:",output_text)
    if flag:
        # 语音合成并播放
        tts(output_text, "./play.wav")
        play(device,"./play.wav")

由于语音识别结果均为汉字,我们有要求要将其中的数字提取出来,也就是九转成9、十二转成12这种,

实现思路就是先将所有汉字单独转为数字,

然后变步长遍历

如果是0,就步长跨1

如果是小于10的数,就与下一位相乘,然后累加到result里,然后步长跨2

如果是大于10的数,直接累计到result里,然后步长跨1

该代码仅适用于正数,代码实现如下

def chinese_char2num(character):
    result = -1
    characters = {"零": 0, "一": 1, "二": 2, "三": 3, "四": 4,
                  "五": 5, "六": 6, "七": 7, "八": 8, "九": 9,
                  "十": 10, "百": 1e2, "千": 1e3, "万": 1e4, }
    for c in characters:
        if character == c:
            result = characters[c]

    return result


def chinese_chars2num(text):
    success = True
    result = 0

    # 将每一个汉字转为数字
    nums = []
    for t in text:
        num = chinese_char2num(t)
        nums.append(num)

    i = 0
    while True:
        if nums[i] < 0:
            success = False
            break
        if nums[i] == 0:
            i += 1

        elif nums[i] < 10:
            if i + 1 < len(nums):
                if nums[i + 1] >= 10:
                    result += nums[i] * nums[i + 1]
            else:
                result += nums[i]
            i += 2
        else:
            result += nums[i]

            i += 1
        if i > len(nums):
            break

    if not success:
        result = -1
    return result


print(chinese_chars2num('一万二千三百四十五'))

最后放一张我们小组最后实现的结果,是一个可以实时显示当前空气质量可以定时提醒,并具有语音交互功能的小电视。